﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Net;
using System.Net.Sockets;
using System.Collections;
using System.Net.NetworkInformation;

namespace eeb_2012_clientserver_server
{
    // State object for reading client data asynchronously
    public class StateObject
    {
        // Client  socket.
        public Socket workSocket = null;
        // Size of receive buffer.
        public const int BufferSize = 1024;
        // Receive buffer.
        public byte[] buffer = new byte[BufferSize];
        // Received data string.
        public StringBuilder sb = new StringBuilder();

        // ConnectionHandler
        public ConnectionHandler chandler = null;
    }

    public class ConnectionHandler
    {
        private StateObject state;
        public bool connected = false;
        private String ipAddress;
        private Int32 port;
        private ManualResetEvent doneDone = new ManualResetEvent(false);
        private ManualResetEvent sendDone = new ManualResetEvent(false);
        private ManualResetEvent receiveDone = new ManualResetEvent(false);

        public static ArrayList conArr = new ArrayList();

        private ArrayList objToSend = null;

        public ConnectionHandler(StateObject stateObj)
        {
            state = stateObj;
            connected = true;
            ipAddress = ((IPEndPoint)state.workSocket.RemoteEndPoint).Address.ToString();
            port = ((IPEndPoint)state.workSocket.RemoteEndPoint).Port;
            ConnectionHandler.conArr.Add(this);

            objToSend = new ArrayList();

            //Console.WriteLine("------------ New Connection Handler created. Processing...");
        }

        public void closeConnection()
        {
            // set connected flag to signal receive/send-threads.
            connected = false;
            // Signal to release receiving thread.
            receiveDone.Set();
            // Signal to release sending thread.
            sendDone.Set();

            // Remove ConnectionHandler from list.
            ConnectionHandler.conArr.Remove(this);

            Console.WriteLine("------------ ConnectionHandler closed: {0}:{1}", getIpString(), getPortInt());
        }

        public static Boolean stopAllThreads()
        {
            // Alle Server-Threads stoppen
            ArrayList stopNow = new ArrayList();
            stopNow = (ArrayList)ConnectionHandler.conArr.Clone();

            for (IEnumerator e = stopNow.GetEnumerator(); e.MoveNext(); )
            {
                ConnectionHandler ch = (ConnectionHandler)e.Current;
                ch.closeConnection();
            }            
            return true;
        }

        public void sendThread()
        {
            while (state.workSocket.Connected && connected)
            {
                sendDone.Reset();
                //Console.WriteLine(objToSend.Count);

                ArrayList sendNow = new ArrayList();
                lock (objToSend.SyncRoot)
                {
                    sendNow = (ArrayList)objToSend.Clone();
                    objToSend.Clear();
                }
                                
                while (sendNow.Count > 0)
                {
                    foreach (String data in sendNow)
                    {

                        Server.Send(state.workSocket, data, state);
                        sendDone.WaitOne();
                    }
                    sendNow.Clear();
                }
                /*Server.Send(state.workSocket, "MuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuh" +
                                              "MuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuh" +
                                              "MuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuh" +
                                              "MuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuh" +
                                              "MuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuh" +
                                              "MuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuh" +
                                              "MuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuh" +
                                              "MuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuh" +
                                              "MuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuh" +
                                              "MuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuh" +
                                              "MuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuh" +
                                              "MuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuh" +
                                              "MuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuh" +
                                              "MuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuh" +
                                              "MuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuh" +
                                              "MuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuh" +
                                              "MuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuh" +
                                              "MuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuh" +
                                              "MuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuh" +
                                              "MuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuh" +
                                              "MuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuh" +
                                              "MuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuh" +
                                              "MuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuh" +
                                              "MuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuh" +
                                              "MuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuh" +
                                              "MuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuh" +
                                              "MuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuh" +
                                              "MuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuh" +
                                              "MuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhMuhM<EOF>", state);*/
                
                Thread.Sleep(20);
            }
            Console.WriteLine("SENDING THREAD ENDED");
        }

        public void receiveThread()
        {
            while (state.workSocket.Connected && connected)
            {
                receiveDone.Reset();
                Server.Receive(state.workSocket, state);
                receiveDone.WaitOne();
                Thread.Sleep(20);
            }
            Console.WriteLine("RECEIVING THREAD ENDED");
        }

        public void setReceiveDone()
        {
            receiveDone.Set();
        }

        public void setSendDone()
        {
            sendDone.Set();
        }

        public static void addData(String data)
        {
            foreach (ConnectionHandler chandl in ConnectionHandler.conArr)
            {
                chandl.objToSend.Add(data);
                Console.WriteLine("Package added!");
            }
        }
        
        public String getIpString()
        {
            return ipAddress;
        }

        public Int32 getPortInt()
        {
            return port;
        }

        public static void printConnectionList()
        {
            if (ConnectionHandler.conArr != null)
            {
                ArrayList connections = ConnectionHandler.conArr;
                if (connections.Count > 0)
                {
                    int i = 1;
                    foreach (ConnectionHandler cHandl in ConnectionHandler.conArr)
                    {
                        Console.WriteLine(" Client {2}: {0}:{1}", cHandl.getIpString(), cHandl.getPortInt(), i);
                        i++;
                    }
                } else
                    Console.WriteLine("Sorry, no client connected!");
            }
            else
            {
                Console.WriteLine("Sorry, no client connected!");
            }
        }
    }

    public class Server
    {
        // Thread signal.
        public static ManualResetEvent allDone = new ManualResetEvent(false);
        private const String SERVERTYPE = "local";
        private const Int32 LOCALPORT = 2000;

        // Server status.
        private static Boolean running = false;

        public Server()
        {
        }

        public static void StopListening()
        {
            // Flag.
            running = false;
            // Signal.
            allDone.Set();
            Console.WriteLine("LISTENING STOPPED!");
        }

        public static void StartListening()
        {
            // Data buffer for incoming data.
            byte[] bytes = new Byte[1024];

            // Establish the local endpoint for the socket.
            // The DNS name of the computer
            // running the listener is "host.contoso.com".
            IPEndPoint localEndPoint = null;
            IPAddress ipAddress = null;

            if(SERVERTYPE.Equals("local"))
            {
                    ipAddress = IPAddress.Loopback;
                    localEndPoint = new IPEndPoint(ipAddress, Server.LOCALPORT);
            }
            else
            {
                IPAddress[] localIPs = Dns.GetHostAddresses(Dns.GetHostName());
                for (int i = 0; i < localIPs.Length; i++)
                {
                    //Console.WriteLine(localIPs[i]);
                }

                ipAddress = localIPs[3];
                localEndPoint = new IPEndPoint(ipAddress, Server.LOCALPORT);
            }

            // Create a TCP/IP socket.
            Socket listener = new Socket(AddressFamily.InterNetwork,
                SocketType.Stream, ProtocolType.Tcp);

            // Bind the socket to the local endpoint and listen for incoming connections.
            try
            {
                Console.WriteLine("{0}:{1}: Trying to bind on socket!", ipAddress, Server.LOCALPORT);
                listener.Bind(localEndPoint);
                listener.Listen(100);
                Console.WriteLine("{0}:{1}: Server started!", ipAddress, Server.LOCALPORT);
                running = true;

                while (running)
                {
                    // Set the event to nonsignaled state.
                    allDone.Reset();

                    // Start an asynchronous socket to listen for connections.
                    Console.WriteLine("{0}:{1}: Waiting for a connection...", ipAddress, 2000);
                    listener.BeginAccept(
                        new AsyncCallback(AcceptCallback),
                        listener);

                    // Wait until a connection is made before continuing.
                    allDone.WaitOne();
                }
                // no more code
                
            }
            catch (Exception e)
            {
                Console.WriteLine(e.ToString());
            }
        }

        public static void AcceptCallback(IAsyncResult ar)
        {
            // Signal the main thread to continue.
            allDone.Set();

            // Get the socket that handles the client request.
            Socket listener = (Socket)ar.AsyncState;
            Socket handler = listener.EndAccept(ar);

            Console.WriteLine("{0}:{1}: New connection accepted!", IPAddress.Parse(((IPEndPoint)handler.RemoteEndPoint).Address.ToString()), ((IPEndPoint)handler.RemoteEndPoint).Port.ToString());

            // Create the state object.
            StateObject state = new StateObject();
            state.workSocket = handler;
            // Create new connection handler.
            ConnectionHandler chandler = new ConnectionHandler(state);
            state.chandler = chandler;

            // Start communication threads.
            Thread receiveing = new Thread(chandler.receiveThread);
            receiveing.Start();
            Thread sending = new Thread(chandler.sendThread);
            sending.Start();
        }

        public static void Receive(Socket handler, StateObject state)
        {
            try
            {
                handler.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0,
                    new AsyncCallback(ReadCallback), state);
            }
            catch (SocketException e)
            {
                Console.WriteLine("Disconnected while receiving: error code {0} - {1}!", e.NativeErrorCode, handler.Connected);

                // Release the socket.
                state.chandler.closeConnection();
                handler.Shutdown(SocketShutdown.Both);
                handler.Close();
            }
        }

        public static void ReadCallback(IAsyncResult ar)
        {
            //Console.WriteLine("*************** ReadCallback IN ***************");
            String content = String.Empty;

            // Retrieve the state object and the handler socket
            // from the asynchronous state object.
            StateObject state = (StateObject)ar.AsyncState;
            Socket handler = state.workSocket;

            int bytesRead = -1;
            try
            {
                //Console.WriteLine("*************** TRYING TO READ STH. ***************");
                // Read data from the client socket. 
                bytesRead = handler.EndReceive(ar);
                //Console.WriteLine("*************** READ STH. ***************");
            }
            catch (SocketException e)
            {
                Console.WriteLine("Disconnected while receiving: error code {0} - {1}!", e.NativeErrorCode, handler.Connected);

                // Release the socket.
                state.chandler.closeConnection();
                handler.Shutdown(SocketShutdown.Both);
                handler.Close();

            }

            if (handler.Connected)
            {
                if (bytesRead > 0)
                {
                    Console.WriteLine("---------- {0}:{1}", IPAddress.Parse(((IPEndPoint)handler.RemoteEndPoint).Address.ToString()), ((IPEndPoint)handler.RemoteEndPoint).Port.ToString());
                    
                    // There  might be more data, so store the data received so far.
                    state.sb.Append(Encoding.ASCII.GetString(
                        state.buffer, 0, bytesRead));

                    // Check for end-of-file tag. If it is not there, read 
                    // more data.
                    content = state.sb.ToString();
                    if (content.IndexOf("<EOF>") > -1)
                    {
                        // All the data has been read from the 
                        // client. Display it on the console.
                        state.chandler.setReceiveDone();
                        
                        //Console.WriteLine("Read {0} bytes from socket. \n Data : {1}",
                          //  content.Length, content);
                        Console.WriteLine("Read {0} bytes from socket.", content.Length);

                        // Clear data for next message.
                        state.sb.Clear();

                        // Echo the data back to the client.
                        //Send(handler, content);
                    }
                    else
                    {
                        // Not all data received. Get more.
                        Console.WriteLine("ReadCallback: Not all data received ({0} bytes). Get more.", content.Length);
                        handler.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0,
                            new AsyncCallback(ReadCallback), state);
                    }
                }
            }
        }

        public static void Send(Socket handler, String data, StateObject state)
        {
            try
            {
                // Convert the string data to byte data using ASCII encoding.
                byte[] byteData = Encoding.ASCII.GetBytes(data);

                // Begin sending the data to the remote device.
                handler.BeginSend(byteData, 0, byteData.Length, 0,
                    new AsyncCallback(SendCallback), state);
            }
            catch (SocketException e)
            {
                Console.WriteLine("Disconnected while sending: error code {0} - {1}!", e.NativeErrorCode, handler.Connected);

                // Release the socket.
                state.chandler.closeConnection();
                handler.Shutdown(SocketShutdown.Both);
                handler.Close();
            }


        }

        private static void SendCallback(IAsyncResult ar)
        {
            //Console.WriteLine("SendCallback");
            StateObject state = (StateObject)ar.AsyncState;
            // Retrieve the socket from the state object.
            Socket handler = state.workSocket;
            try
            {
                // Complete sending the data to the remote device.
                int bytesSent = handler.EndSend(ar);
                Console.WriteLine("---------- {0}:{1}", IPAddress.Parse(((IPEndPoint)handler.RemoteEndPoint).Address.ToString()), ((IPEndPoint)handler.RemoteEndPoint).Port.ToString());
                Console.WriteLine("Sent {0} bytes to client.", bytesSent);

                // Signal Thread.
                state.chandler.setSendDone();
            }
            catch (SocketException e)
            {
                Console.WriteLine("Disconnected while sending: error code {0} - {1}!", e.NativeErrorCode, handler.Connected);

                // Release the socket.
                state.chandler.closeConnection();
                handler.Shutdown(SocketShutdown.Both);
                handler.Close();
            }
        }

        public static void Main(String[] args)
        {
            // StartListening();
            Thread th = new Thread(new ThreadStart(StartListening));
            th.Start();

            // UI
            String cmd = "";
            while (!cmd.ToLower().Equals("stop"))
            {
                cmd = Console.ReadLine();
                if (cmd.ToLower().Equals("if"))
                {
                    //Console.WriteLine();
                    printInterfaces();
                }
                else if (cmd.ToLower().Equals("list"))
                    ConnectionHandler.printConnectionList();
                else if (cmd.ToLower().Equals("paket"))
                    ConnectionHandler.addData("Daten");
                else if (!cmd.ToLower().Equals("stop"))
                    Console.WriteLine("Unbekannter Befehl: " + cmd);
            }

            // Stop Server Listening
            StopListening();
            Console.WriteLine("Listening stopped!");
            // Stop Client threads
            ConnectionHandler.stopAllThreads();
            Console.WriteLine("Client threads stopped!");
            // Stop Main thread
            Console.WriteLine("Goodbye");
            th.Abort();

            //return 0;
        }

        public static void printInterfaces()
        {
            NetworkInterface[] nics = NetworkInterface.GetAllNetworkInterfaces();

            foreach (NetworkInterface ni in nics) { 
                foreach (UnicastIPAddressInformation x in ni.GetIPProperties().UnicastAddresses) { 
                    if (x.Address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork) { 
                        Console.WriteLine(" IPAddress ........ : {0:x}", x.Address.ToString()); 
                    }
                }
            }
        }

    }
}
